#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <math.h>
#include <time.h>
#include <errno.h>
#include <memory.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

#include "server_cfg.h"
#include "queue.h"

typedef struct {
    pthread_t tid;
    QueueInt qe;
}Worker;

static Worker _myWorker[MAX_WORKER];
static int  _mainSfd = 0;
static int  _serverStatus = SERVER_NORMAL;

extern void parseReq(char* req, ssize_t size, char* resp, ssize_t* respSz);

int initServer() {
    struct sockaddr_in myAddr;
    myAddr.sin_family = AF_INET;
    myAddr.sin_port = htons(LISTEN_PORT);
    myAddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(myAddr.sin_zero), 8);
    
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    
    if (bind(sfd, (struct sockaddr *)&myAddr, sizeof(struct sockaddr)) == -1) {
        perror("!failed_to_connect");
        exit(1);
    }
    
    if (listen(sfd, MAX_WORKER) == -1) {
        perror("!failed_to_listen_on_port");
        exit(1);
    }

    printf("#init_server@%d...ok\n", LISTEN_PORT);
    return sfd;
}

void closeSocket(int* sfd) {
    shutdown(*sfd, SHUT_RDWR);
    close(*sfd);
    *sfd = 0;
}

void haltNow() {
    _serverStatus = SERVER_HALT;
    closeSocket(&_mainSfd);
    printf("#halt_status_has_been_set\n");
}

void completeOneReq(int currSfd) {
    int  waitTimes = 0;
    char reqBuf[REQ_SIZE];
    char respBuf[RESP_SIZE];
    ssize_t respSize = 0;
    
    memset(reqBuf, 0, REQ_SIZE*sizeof(char));
    memset(respBuf, 0, RESP_SIZE*sizeof(char));
    errno = 0;
    
    while (waitTimes * RETRY_INTERVAL < TIMEOUT) {
        ssize_t sz = recv(currSfd, reqBuf, REQ_SIZE*sizeof(char), MSG_DONTWAIT);
        if (sz != -1) {
            memset(respBuf, 0, RESP_SIZE*sizeof(char));
            parseReq(reqBuf, sz, respBuf, &respSize);
            send(currSfd, respBuf, respSize, 0);
            break;
        } else {
            if (errno == EAGAIN) {
                waitTimes++;
            }
        }
        usleep(RETRY_INTERVAL);
    }

    perror("!one_req_completed_or_time_out");
}
 
void* workerDaemon(void *param) {
    int  currSfd = NULL_SFD;

    Worker* worker = (Worker*)param;
    printf("#thread:%#lX_started\n",worker->tid);
    
    while (_serverStatus != SERVER_HALT) {
        usleep(RETRY_INTERVAL);
        if (currSfd == NULL_SFD) {
            if (worker->qe.count != 0) {
                currSfd = dequeueInt(&worker->qe);
            } else {
                continue;
            }
        }
        completeOneReq(currSfd);
        closeSocket(&currSfd);
    }
    
    if (currSfd != 0) {
        closeSocket(&currSfd);
    }
    pthread_exit(0);
}

Worker* pickOneFreeWorker() {
    int minQueue = QUEUE_SIZE;
    int workerIndex = 0;
    for (int i = 0; i < MAX_WORKER; i++) {
        if (_myWorker[i].qe.count < minQueue) {
            minQueue = _myWorker[i].qe.count;
            workerIndex = i;
        }
    }
    
    return minQueue == QUEUE_SIZE ? NULL : &_myWorker[workerIndex];
}

void dispatch(int sfd) {
    int clientSfd = 0;
    socklen_t addrLen = sizeof(struct sockaddr_in);
    struct sockaddr_in remoteAddr;
    
    while (_serverStatus != SERVER_HALT) {
        if ((clientSfd = accept(sfd, (struct sockaddr *)&remoteAddr, &addrLen)) == -1) {
            perror("!failed_to_accept_one_connection.");
            continue;
        }
        printf("established\n");
        Worker* worker = pickOneFreeWorker();
        if (worker != NULL) {
            enqueueInt(&worker->qe, clientSfd);
        } else {
            perror("!no_free_worker_available");
            usleep(1);
        }
    }
}

void initWorker() {
    int rc = 0;
    for (int i = 0; i < MAX_WORKER; i++) {
        initQueue(&_myWorker[i].qe);
        rc = pthread_create(&_myWorker[i].tid, NULL, workerDaemon, &_myWorker[i]);
        if(rc) {
            printf("!failed_to_start_daemon_thread::%s:%d\n", __func__, rc);
        }
    }
}

void clearAll() {
    printf("do_cleaning_now\n");
    for (int i = 0; i < MAX_WORKER; i++) {
        int s = dequeueInt(&_myWorker[i].qe);
        while (s != NULL_SFD) {
            closeSocket(&s);
            s = dequeueInt(&_myWorker[i].qe);
        }
        int *status = NULL;
        pthread_join(_myWorker[i].tid, (void**)&status);
        printf("thread:%#lX_stopped_successfully\n", _myWorker[i].tid);
    }
}

int main(void) { 
	initWorker();
	_mainSfd = initServer();
	if(_mainSfd <= 0) {
        perror("fatal_err..exiting\n");
        exit(0);
	}

	dispatch(_mainSfd);
	clearAll();
	return 0;
}
